using System.Collections;
using System.Collections.Generic;
using System.Text;
using System.IO;
using System;

using Athena.Core;
using Athena.Objects;
using System.Text.RegularExpressions;

namespace Athena.IO
{
    public class ModuleReaderWriter : IModelWriter
    {
        public override void WriteModel(Stream oStream, Model oModel)
        {
            WriteModule(oModel.BackEndModel, new StreamWriter(oStream));
        }
        #region reading layout -- not needed
        /*
        /// <summary>
        /// Writes the relative locations of all the drawables inside the given module
        /// </summary>
        /// <param name="module"></param>
        /// <param name="oStream"></param>
        public static void WriteModuleLayout(Module module, StreamWriter oStream)
        {
            if (module.Drawable == null || !(module.Drawable is Objects.ContainerGlyph)) return;

            System.Drawing.PointF refpt = module.Drawable.Location;

            oStream.WriteLine(module.Drawable.Dimensions.Width + " " + module.Drawable.Dimensions.Height);

            foreach (IDrawable n in ((Objects.ContainerGlyph)module.Drawable).Drawables)
            {
                if (n.Item is Node)
                {
                    System.Drawing.PointF point = Utility.SubtractPoints(n.Location, refpt);
                    oStream.WriteLine(n.ID + " " + point.X + " " + point.Y + " " + n.Dimensions.Width + " " + n.Dimensions.Height);
                }                
            }
            
            oStream.WriteLine();

            Hashtable edges = module.AllReactions;
            foreach (Edge e in edges.Values)
            {
                if (e.Drawable != null && e.Drawable is BezierConnector)
                {
                    BezierConnector connector = (BezierConnector)e.Drawable;
                    oStream.WriteLine(connector.ID);

                    foreach (BezierConnector.CurveSegment curve in connector.CurveSegments)
                    {
                        oStream.Write(curve.StartNode.ID);
                        if (curve.IsStraight)
                        {
                            foreach (BezierConnector.CurveSegment.Segment subsegment in curve.Segments)
                            {
                                oStream.Write(";" + (subsegment.Start.X - refpt.X) + " " + (subsegment.Start.Y - refpt.Y) +                                              
                                              " " + (subsegment.End.X - refpt.X) + " " + (subsegment.End.Y - refpt.Y) + ";");
                            }
                        }
                        else
                        {
                            foreach (BezierConnector.CurveSegment.Segment subsegment in curve.Segments)
                            {
                                oStream.Write(";" + (subsegment.Start.X - refpt.X) + " " + (subsegment.Start.Y - refpt.Y) +
                                              " " + (subsegment.ControlPoint1.X - refpt.X) + " " + (subsegment.ControlPoint1.Y - refpt.Y) +
                                              " " + (subsegment.ControlPoint2.X - refpt.X) + " " + (subsegment.ControlPoint2.Y - refpt.Y) +
                                              " " + (subsegment.End.X - refpt.X) + " " + (subsegment.End.Y - refpt.Y) + ";");
                            }
                        }
                        oStream.WriteLine();
                    }
                    oStream.WriteLine();
                }
            }
        }
        
        public static void ReadModuleLayout(Module module, StreamReader iStream)
        {
            if (module.Drawable == null) return;

            System.Drawing.PointF refpt = module.Drawable.Location;

            string line = "";

            while (!iStream.EndOfStream && string.IsNullOrEmpty(line)) line = iStream.ReadLine();
            if (iStream.EndOfStream) return;

            string[] words = line.Split(' ');
            if (words.Length != 2) return;

            module.Drawable.Dimensions = new System.Drawing.SizeF(float.Parse(words[0]), float.Parse(words[1]));

            List<Item> visited = new List<Item>();
            bool aliases = false;

            while (!iStream.EndOfStream)
            {
                if (string.IsNullOrEmpty(line = iStream.ReadLine())) break;

                words = line.Split(' ');

                if (words.Length < 5) return;

                string id = words[0], x = words[1], y = words[2], w = words[3], h = words[4];

                Item item = module.FindItem(id);

                if (item != null && item.Drawable != null)
                {
                    System.Drawing.PointF location = new System.Drawing.PointF(float.Parse(x) + refpt.X, float.Parse(y) + refpt.Y);
                    System.Drawing.SizeF dimension = new System.Drawing.SizeF(float.Parse(w), float.Parse(h));

                    if (visited.Contains(item) || item.Drawable.Parent != module.Drawable)
                    {
                        aliases = true;
                        int count1 = 0, count2 = 0;
                        foreach (Item it in visited)
                        {
                            if (it == item) ++count1;
                        }
                        foreach (IDrawable i in item.Drawable.Alias)
                        {
                            if (i.Parent == null) ++count2;
                        }
                        if (count1 <= 1+count2)
                        {
                            for (int i=0; i < item.Drawable.Alias.Count; ++i)
                            {
                                if (item.Drawable.Alias[i].Parent == null)
                                {
                                    item.Drawable.Alias[i].Parent = module.Drawable;
                                    item.Drawable.Alias[i].Location = location;
                                    item.Drawable.Alias[i].Dimensions = dimension;
                                    break;
                                }
                            }
                        }
                        else
                        {
                            IDrawable alias = item.Drawable.Clone() as IDrawable;
                            if (alias != null)
                            {
                                alias.Location = location;
                                alias.Dimensions = dimension;
                                item.Drawable.AddAlias(alias);
                                if (module.Drawable is Objects.ContainerGlyph)
                                {
                                    ((Objects.ContainerGlyph)module.Drawable).Drawables.Add(alias);
                                    alias.Parent = module.Drawable;
                                }
                            }
                        }
                    }
                    else
                    {
                        item.Drawable.Location = location;
                        item.Drawable.Dimensions = dimension;
                    }
                    visited.Add(item);
                }
            }
            
            Hashtable reactions = module.AllReactions;
            if (aliases)
                foreach (Edge e in reactions.Values)
                {
                    if (e.Drawable is Connector)
                    {
                        Node[] outputs = e.Products;
                        //((Connector)e.Drawable).ReplaceNodesWithNearestAlias();
                        ((Connector)e.Drawable).MakePretty();
                        if (outputs.Length == 1 && e.Drawable is BezierConnector &&
                            (outputs[0] == Core.Module.Waste || outputs[0] == Core.Module.Source))
                        {
                            ((BezierConnector)e.Drawable).StraightLines = true;
                        }
                    }
                }

            //int i = 0;
            while (!iStream.EndOfStream)   //connectors
            {
                if (string.IsNullOrEmpty(line = iStream.ReadLine())) break;

                Item item = module.FindItem(line);
                
                //item = reactions[i];
                //++i;

                if (item != null && item.Drawable is BezierConnector)
                {
                    ReadConnectorLayout(item.Drawable as BezierConnector, iStream, refpt);
                }
            }
        }
        static void ReadConnectorLayout(BezierConnector connector, StreamReader iStream, System.Drawing.PointF refpt)
        {
            if (connector == null) return;

            string line = iStream.ReadLine();

            List<BezierConnector.CurveSegment> visited = new List<BezierConnector.CurveSegment>();

            while (!string.IsNullOrEmpty(line))
            {
                string[] words = line.Split(';');
                if (words.Length > 1)
                {
                    List<BezierConnector.CurveSegment> candidates = connector.FindCurveSegments(words[0]);
                    foreach (BezierConnector.CurveSegment curve in candidates)
                    {
                        if (!visited.Contains(curve))
                        {
                            curve.Segments.Clear();
                            for (int i = 1; i < words.Length; ++i)
                            {
                                string[] words1 = words[i].Split(' ');
                                if (words1.Length == 8)
                                {
                                    curve.Segments.Add(new BezierConnector.CurveSegment.Segment(
                                        new System.Drawing.PointF(float.Parse(words1[0]) + refpt.X, float.Parse(words1[1]) + refpt.Y),
                                        new System.Drawing.PointF(float.Parse(words1[2]) + refpt.X, float.Parse(words1[3]) + refpt.Y),
                                        new System.Drawing.PointF(float.Parse(words1[4]) + refpt.X, float.Parse(words1[5]) + refpt.Y),
                                        new System.Drawing.PointF(float.Parse(words1[6]) + refpt.X, float.Parse(words1[7]) + refpt.Y)));
                                    curve.IsStraight = false;
                                }
                                else
                                {
                                    if (words1.Length == 4)
                                    {
                                        curve.Segments.Add(new BezierConnector.CurveSegment.Segment(
                                            new System.Drawing.PointF(float.Parse(words1[0]) + refpt.X, float.Parse(words1[1]) + refpt.Y),
                                            new System.Drawing.PointF(float.Parse(words1[0]) + refpt.X, float.Parse(words1[1]) + refpt.Y),
                                            new System.Drawing.PointF(float.Parse(words1[2]) + refpt.X, float.Parse(words1[3]) + refpt.Y),
                                            new System.Drawing.PointF(float.Parse(words1[2]) + refpt.X, float.Parse(words1[3]) + refpt.Y)));
                                        curve.IsStraight = true;
                                    }
                                }
                            }
                            visited.Add(curve);
                            break;
                        }
                    }
                }
                line = iStream.ReadLine();
            }
            connector.FixMiddleSegment();
        }
        */
        #endregion
        /// <summary>
        /// produces a file of the standard format
        /// </summary>
        /// <param name="oStream"></param>
        /// <param name="oModel"></param>
        public static void WriteModule(Module module, StreamWriter oStream)
        {
            if (module == null) return;

            oStream.WriteLine(module.ModularScript);

            //WriteModuleLayout(module, oStream);
        }

        public static Item MakeNewInstance(string type)
        {
            return (Item)Activator.CreateInstance(Type.GetType(type));
        }

        /// <summary>
        /// Loads a module from a file in the standard format.
        /// Uses: AssignModuleTitle
        /// </summary>
        /// <param name="oStream"></param>
        /// <param name="model"></param>
        /// <returns></returns>
        public static List<Item> ReadModule(StreamReader iStream, Core.Module model)
        {
            Module module = new Module("module","no_title");
            List<Item> items = new List<Item>();
            
            string line = "";
            
            while (string.IsNullOrEmpty(line) && !iStream.EndOfStream) line = iStream.ReadLine();
            if (iStream.EndOfStream) return items;

            string title = line;
            line = iStream.ReadLine().Trim(' ', ';');

            string line2 = line.ToLower();

            while (!(iStream.EndOfStream && string.IsNullOrEmpty(line)) && !line2.Contains("end"))
            {
                string line2start = line2;
                if (line2.Length > 11) line2start = line2.Substring(0, 11);

                if (!ReadVars(module, line))
                {
                    if (line2.Contains("->"))
                        ReadConnections(module, line);
                    
                    if (line2.Contains(",") && line2.Contains(":"))
                        ReadSequence(module, line);

                    if (line2.Contains("("))
                    {
                        ReadCall(module, line, model);
                    }

                    if (line2.Contains("="))
                    {
                        ReadParm(module, line);
                    }
                }

                line = iStream.ReadLine().Trim(' ',';');
                line2 = line.ToLower();
            }

            AssignModuleTitle(module, title);

            module.GenerateDrawable(new System.Drawing.PointF(Utility.Panel.Location.X + Utility.Panel.Size.Width / 2, Utility.Panel.Location.Y + Utility.Panel.Size.Height / 2));
            module.GenerateDegradationDrawables();

            items.Add(module);
            return items;
        }

        /// <summary>
        /// Parses the title from a module file
        /// </summary>
        /// <param name="module"></param>
        /// <param name="line"></param>
        static void AssignModuleTitle(Module module, string line)
        {
            line.Trim();
            string[] words1 = line.Split(' ');
            if (words1.Length > 1)
                line = line.Substring(words1[0].Length).Trim();
            
            if (line.Contains("="))
            {
                string[] words = line.Split('=');
                string[] outputs = words[0].Trim().Split(',', ' ');
                string[] rest = words[1].Trim().Split('(', ',', ')');

                module.ID = rest[0];
                /*
                foreach (string s in outputs)
                {
                    Node n = module.FindItem(s) as Node;
                    if (n != null)
                        module.Mappings.Add(n, null);
                }
                for (int i = 1; i < rest.Length; ++i)
                {
                    Node n = module.FindItem(rest[i]) as Node;
                    if (n != null)
                        module.Mappings.Add(n, null);
                }*/
            }
            else
            {
                module.ID = line;
            }
        }

        static void ReadParm(Module module, string line)
        {   
            line = line.Replace(" ", "");
            line = line.Replace(";", "");
            string[] words = line.Split('=');

            if (words.Length != 2)
                return;

            try
            {
                module.SetParameter(words[0].Trim(),double.Parse(words[1].Trim()));
            }
            catch (Exception) {}
        }
        /// <summary>
        /// Parses the function all statements in a module file
        /// </summary>
        /// <param name="module"></param>
        /// <param name="line"></param>
        /// <param name="model"></param>
        /// <returns></returns>
        static void ReadCall(Module module, string line, Core.Module model)
        {
            string[] args = line.Split(new char[] {'(',')'}, StringSplitOptions.RemoveEmptyEntries);

            if (args.Length < 2) return;

            string name = args[0];

            string[] words = args[1].Split(new char[] {','}, StringSplitOptions.RemoveEmptyEntries);

            Module module2 = model.FindItem(name.Trim()) as Module;
            if (module2 == null) return;
            
            foreach (string s in words)
            {
                string[] two = s.Split(new char[] {'='}, StringSplitOptions.RemoveEmptyEntries);
                if (two.Length == 2)
                {
                    module2.SetMapping( module2.FindItem(two[0],false), model.FindItem(two[1],false) );
                }
            }

        }
        /// <summary>
        /// Parses the parts left/right information from a module file
        /// </summary>
        /// <param name="module"></param>
        /// <param name="line"></param>
        static void ReadSequence(Module module, string line)
        {
            string[] twoparts = line.Split(':',';');
            string rate = "1";
            
            if (twoparts.Length == 2) 
                rate = twoparts[1].Replace(';',' ');

            if (twoparts.Length > 2)
                rate = twoparts[2].Replace(';', ' ');

            string[] chain = twoparts[0].Split(',');

            for (int i = 0; i < chain.Length - 1; ++i)
            {
                Item item1 = module.FindItem(chain[i].Trim()), item2 = module.FindItem(chain[i + 1].Trim());
                if (item1 is Part && item2 is Part)
                {
                    ((Part)item1).Right = (Part)item2;
                }
            }
            Item item3 = module.FindItem(chain[0].Trim());
            if (item3 is Part)
            {
                ((Part)item3).PoPS.Rate = rate; 
            }
        }
        /// <summary>
        /// Parses a reaction statement from a module file
        /// </summary>
        /// <param name="module"></param>
        /// <param name="line"></param>
        static void ReadConnections(Module module, string line)
        {
            string[] words;
            string name,id = "";

            List<Node> inputs = new List<Node>(), outputs = new List<Node>();
            if (line.Contains(":"))
            {
                words = line.Split(':');
                string[] words2 = words[0].Split(',');
                id = words2[words2.Length - 1].Trim();
                Part gene = module.FindItem(id) as Part;
                if (gene != null)
                {
                    inputs.Add(gene);
                    id = "J0";
                }
                line = words[1].Trim();
            }
            line = line.Replace("->", ";");
            words = line.Split(';');   //inputs ; outputs ; rate

            if (words.Length > 2)
            {
                string[] words1 = words[0].Split('+');  //inputs                    

                for (int i = 0; i < words1.Length; ++i)
                    if (words1[i].Length > 0)
                    {
                        name = words1[i].Trim();

                        Item n = module.FindItem(name);
                        if (n != null && n is Node)
                            inputs.Add(n as Node);
                    }

                words1 = words[1].Split('+');  //outputs

                for (int i = 0; i < words1.Length; ++i)
                    if (words1[i].Length > 0)
                    {
                        name = words1[i].Trim();

                        Item n = module.FindItem(name);
                        if (n != null && n is Node)
                            outputs.Add(n as Node);
                    }

                string rate = words[2].Trim();   //rate

                Part gene = module.FindItem(rate) as Part;
                if (gene != null)
                {
                    if (!inputs.Contains(gene))
                        inputs.Add(gene);
                }

                if (inputs.Count == 0)
                    inputs.Add(Core.Module.Empty);
                else
                    inputs.Remove(Core.Module.Empty);

                if (outputs.Count == 0)
                    outputs.Add(Core.Module.Empty);
                else
                    outputs.Remove(Core.Module.Empty);

                if (!string.IsNullOrEmpty(rate))
                {
                    Edge e = new Edge(inputs, outputs, rate);
                    foreach (Node n in outputs)
                    {
                        if (n is Part)
                            ((Part)n).PoPS.Rate = e.Rate;
                    }
                    
                    if (!string.IsNullOrEmpty(id)) e.ID = id;

                    //if (!string.IsNullOrEmpty(parms)) e.SetParameterInitializations(parms,',');

                    module.AddItem(e);
                }

            }
        }
        /// <summary>
        /// Parses the variable declarations from a module file
        /// </summary>
        /// <param name="module"></param>
        /// <param name="line"></param>
        static bool ReadVars(Module module, string line)
        {
            line = line.Trim();

            if (string.IsNullOrEmpty(line)) return false;

            Random runif = new Random();

            string[] words = line.Split(' ', ',');
            if (words.Length < 2) return false;

            string type = words[0].ToLower(), name = words[1];

            if (words.Length > 1)
                for (int i = 1; i < words.Length; ++i)
                {
                    name = words[i].Trim();
                    if (name.Length > 0)
                        switch (type)
                        {
                            case "gene":
                            case "genes":
                                module.AddItem(new Gene(name, "AGTGCGATCAGTTTAAGTGCGATCAGTGAGTGCGATCAGTGCGATCGATCGATTAGATCGATTACGATCGAGTGCGATCAGTGAGTGCGATCAGTGCGATCGATCGATTAGATCGATTACGATCGAGTGCGATCAGTGAGTGCGATCAGTGCGATCGATCGATTAGATCGATTACGATCGAAGTGCGATCAGTGAGTGCGATCAGTGCGATCGATCGATTAGATCGATTACGATCGATCGATTAGATCGATTATCGATTAGATCGATTAATCGATTAGATCGATTAATCGATTAGATCGATTA"));
                                break;
                            case "promoter":
                            case "promoters":
                                module.AddItem(new Promoter(name, "cgcgacaaccGGAATATGAAAGCAAAGCGCAGCGTCTGaataacgttt"));
                                break;
                            case "terminator":
                            case "terminators":
                                module.AddItem(new Terminator(name, "taatcattctTAGCGTGACCGGGAAGTCGGTCACGCTAcctcttctga"));
                                break;
                            case "rbs":
                            case "part":
                            case "parts":
                                module.AddItem(new Part("part", name, "ATGAAACGCATTAGCACCACCATTACCACCACCATCACCATTACCACAGGTAACGGTGCGGGCTGA"));
                                break;
                            case "species":
                            case "protein":
                            case "proteins":
                                module.AddItem(new Species("species", name, 0));
                                break;
                            case "compartment":
                                break;
                            case "module":
                                break;
                            default:
                                return false;
                        }
                }
            return true;
        }
    }
}
